//
// This code was created by Jeff Molofee '99 (ported to Linux/GLUT by Richard Campbell '99)
//
// If you've found this code useful, please let me know.
//
// Visit me at www.demonews.com/hosted/nehe 
// (email Richard Campbell at ulmont@bellsouth.net)
//
#include <GL/glut.h>    // Header File For The GLUT Library 
#include <GL/gl.h>	// Header File For The OpenGL32 Library
#include <GL/glu.h>	// Header File For The GLu32 Library
#include <unistd.h>     // Header file for sleeping.
#include <stdio.h>
#include <memory.h>	//Header File For memset

/* ascii code for the escape key */
#define ESCAPE 27

/* The number of our GLUT window */
int window; 

typedef short int bool;
#define true	0x01
#define false	0x00


bool	masking = true;			// 是否使用“掩模”
bool	mp = false;				// 键M是否按下
bool	sp = false;				// 空格是否按下
bool	scene = false;			// 绘制哪一个场景

GLfloat     roll = 0.0f;		// 滚动纹理

GLuint      texture[5];	// 保存5个纹理
GLuint		loop;		// 循环变量

struct Image {
	unsigned long sizeX;
	unsigned long sizeY;
	char* data;
};
typedef struct Image Image;

int ImageLoad(char* filename, Image* image)
{
	FILE* file;
	unsigned long size;
	unsigned long i;
	unsigned short int planes;
	unsigned short int bpp;
	char temp;

	if ((file = fopen(filename, "rb")) == NULL)
	{
		printf("File Not Found: %s\n", filename);
		return 0;
	}

	fseek(file, 18, SEEK_CUR);

	if((i = fread(&image->sizeX, 4, 1, file)) != 1)
	{
		printf("Error reading width from %s.\n", filename);
		return 0;
	}
	printf("Width of %s: %lu\n", filename, image->sizeX);

	if((i = fread(&image->sizeY, 4, 1, file)) != 1)
	{
		printf("Error reading height from %s.\n", filename);
		return 0;
	}
	printf("Height of %s: %lu\n", filename, image->sizeY);

	size = image->sizeX * image->sizeY * 3;

	if ((i = fread(&planes, 2, 1, file)) != 1)
	{
		printf("Planes from %s is not 1: %u\n", filename, planes);
		return 0;
	}

	if ((i = fread(&bpp, 2, 1, file)) != 1)
	{
		printf("Error reading bpp from %s.\n", filename);
		return 0;
	}

	if(bpp != 24)
	{
		printf("Bpp from %s is not 24: %u\n", filename, bpp);
		return 0;
	}

	fseek(file, 24, SEEK_CUR);

	image->data = (char*)malloc(size);
	if(image->data == NULL)
	{
		printf("Error allocation memory for color-corrected image data");
		return 0;
	}

	if((i = fread(image->data, size, 1, file)) != 1)
	{
		printf("Error readint image data from %s.\n", filename);
		return 0;
	}

	for (i = 0; i < size; i += 3)
	{
		temp = image->data[i];
		image->data[i] = image->data[i+2];
		image->data[i+2] = temp;
	}

	fclose(file);

	return 1;
}


bool LoadGLTextures()
{
	bool status = false;
	Image* image[5];

	memset(image, 0, sizeof(void *) * 5);
	if(image == NULL)
	{
		printf("Error allocating space for image.");
		exit(0);
	}
	for (loop = 0; loop < 5; ++loop)
	{
		image[loop] = (Image*)malloc(sizeof(Image));
		memset(image[loop], 0, sizeof(Image));
	}

	if (ImageLoad("Data/lesson20/logo.bmp", image[0]) &&
			ImageLoad("Data/lesson20/mask1.bmp", image[1]) &&
			ImageLoad("Data/lesson20/image1.bmp", image[2]) &&
			ImageLoad("Data/lesson20/mask2.bmp", image[3]) &&
			ImageLoad("Data/lesson20/image2.bmp", image[4])
	   )
	{
		status = true;
		glGenTextures(5, &texture[0]);
		for (loop = 0; loop < 5; ++loop)
		{
			glBindTexture(GL_TEXTURE_2D, texture[loop]);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexImage2D(GL_TEXTURE_2D, 0, 3, image[loop]->sizeX, image[loop]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, image[loop]->data);
		}
	}

	for (loop = 0; loop < 5; ++loop)
	{
		if (image[loop])
		{
			if (image[loop]->data)
			{
				free(image[loop]->data);
			}
			free(image[loop]);
		}
	}
	return status;
}

/* A general OpenGL initialization function.  Sets all of the initial parameters. */
void InitGL(int Width, int Height)	        // We call this right after our OpenGL window is created.
{
	if(LoadGLTextures() == false)
	{
		fprintf(stdout, "加载纹理资源出错.\n");
		exit(-1);
	}
	glEnable(GL_TEXTURE_2D);
	glClearColor(0.0f, 0.0f, 1.0f, 0.0f);		// This Will Clear The Background Color To Black
	glClearDepth(1.0);				// Enables Clearing Of The Depth Buffer
	glDepthFunc(GL_LESS);				// The Type Of Depth Test To Do
	glEnable(GL_DEPTH_TEST);			// Enables Depth Testing
	glShadeModel(GL_SMOOTH);			// Enables Smooth Color Shading

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();				// Reset The Projection Matrix

	gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);	// Calculate The Aspect Ratio Of The Window

	glMatrixMode(GL_MODELVIEW);
}

/* The function called when our window is resized (which shouldn't happen, because we're fullscreen) */
void ReSizeGLScene(int Width, int Height)
{
	if (Height==0)				// Prevent A Divide By Zero If The Window Is Too Small
		Height=1;

	glViewport(0, 0, Width, Height);		// Reset The Current Viewport And Perspective Transformation

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);
	glMatrixMode(GL_MODELVIEW);
}

/* The main drawing function. */
void DrawGLScene()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		// Clear The Screen And The Depth Buffer
	glLoadIdentity();				// Reset The View
	glTranslatef(0.0f, 0.0f, -2.0f); // 物体移入屏幕2个单位

	glBindTexture(GL_TEXTURE_2D, texture[0]);

	glBegin(GL_QUADS);
	glTexCoord2f(0.0f, -roll+0.0f); glVertex3f(-1.1f, -1.1f,  0.0f);
	glTexCoord2f(3.0f, -roll+0.0f);   glVertex3f( 1.1f, -1.1f,  0.0f);
	glTexCoord2f(3.0f, -roll+3.0f);   glVertex3f( 1.1f,  1.1f,  0.0f);
	glTexCoord2f(0.0f, -roll+3.0f);   glVertex3f(-1.1f,  1.1f,  0.0f);
	glEnd();

	glEnable(GL_BLEND);
	glDisable(GL_DEPTH_TEST);

	if (masking)
	{
		//这个混合系数使得，任何对应“掩模”黑色的部分会变为黑色，白色的部分会保持原来的颜色。
		glBlendFunc(GL_DST_COLOR, GL_ZERO);// 使用黑白“掩模”混合屏幕颜色
	}

	if (scene == true)
	{
		glTranslatef(0.0f, 0.0f, -1.0f);
		glRotatef(roll*360, 0.0f, 0.0f, 1.0f);
		if (masking == true)
		{
			glBindTexture(GL_TEXTURE_2D, texture[3]);
			glBegin(GL_QUADS);					// 开始绘制四边形
			glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.1f, -1.1f,  0.0f);	
			glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.1f, -1.1f,  0.0f);	
			glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.1f,  1.1f,  0.0f);	
			glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.1f,  1.1f,  0.0f);	
			glEnd();
		}
		glBlendFunc(GL_ONE, GL_ONE);				// 把纹理2复制到屏幕
		glBindTexture(GL_TEXTURE_2D, texture[4]);			// 选择第二个纹理
		glBegin(GL_QUADS);						// 绘制四边形
		glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.1f, -1.1f,  0.0f);	
		glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.1f, -1.1f,  0.0f);	
		glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.1f,  1.1f,  0.0f);	
		glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.1f,  1.1f,  0.0f);	
		glEnd();
	}
	
	else
	{
		if (masking)							// “掩模”是否打开
		{
			glBindTexture(GL_TEXTURE_2D, texture[1]);		// 选择第一个“掩模”纹理
			glBegin(GL_QUADS);					// 开始绘制四边形
			glTexCoord2f(roll+0.0f, 0.0f); glVertex3f(-1.1f, -1.1f,  0.0f);	
			glTexCoord2f(roll+4.0f, 0.0f); glVertex3f( 1.1f, -1.1f,  0.0f);	
			glTexCoord2f(roll+4.0f, 4.0f); glVertex3f( 1.1f,  1.1f,  0.0f);	
			glTexCoord2f(roll+0.0f, 4.0f); glVertex3f(-1.1f,  1.1f,  0.0f);	
			glEnd();						
		}
		glBlendFunc(GL_ONE, GL_ONE);					// 把纹理1复制到屏幕
		glBindTexture(GL_TEXTURE_2D, texture[2]);				// 选择第一个纹理
		glBegin(GL_QUADS);							// 开始绘制四边形
		glTexCoord2f(roll+0.0f, 0.0f); glVertex3f(-1.1f, -1.1f,  0.0f);	
		glTexCoord2f(roll+4.0f, 0.0f); glVertex3f( 1.1f, -1.1f,  0.0f);	
		glTexCoord2f(roll+4.0f, 4.0f); glVertex3f( 1.1f,  1.1f,  0.0f);	
		glTexCoord2f(roll+0.0f, 4.0f); glVertex3f(-1.1f,  1.1f,  0.0f);	
		glEnd();
	}

	glEnable(GL_DEPTH_TEST);							// 启用深度测试
	glDisable(GL_BLEND);

	roll += 0.002f;
	if (roll > 1.0f)
	{
		roll -= 1.0f;
	}

	// since this is double buffered, swap the buffers to display what just got drawn.
	glutSwapBuffers();
}

/* The function called whenever a key is pressed. */
void keyPressed(unsigned char key, int x, int y) 
{
	/* avoid thrashing this procedure */
	usleep(100);

	switch(key){
		case ESCAPE:{/* If escape is pressed, kill everything. */
						/* shut down our window */
						glutDestroyWindow(window); 

						/* exit the program...normal termination. */
						exit(0);                   
					}
					break;
		case ' ':{

					 scene = scene == true? false: true;
					 glutPostRedisplay();
				 }
				 break;
		case 'M':
		case 'm':
				 {
					 masking = masking == true? false: true;
					 glutPostRedisplay();
				 }
				 break;
		default:
				 break;
	}
}

int main(int argc, char **argv) 
{  
	/* Initialize GLUT state - glut will take any command line arguments that pertain to it or 
	   X Windows - look at its documentation at http://reality.sgi.com/mjk/spec3/spec3.html */  
	glutInit(&argc, argv);  

	/* Select type of Display mode:   
	   Double buffer 
	   RGBA color
	   Alpha components supported 
	   Depth buffer */  
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH);  

	/* get a 640 x 480 window */
	glutInitWindowSize(640, 480);  

	/* the window starts at the upper left corner of the screen */
	glutInitWindowPosition(0, 0);  

	/* Open a window */  
	window = glutCreateWindow("Jeff Molofee's GL Code Tutorial ... NeHe '99");  

	/* Register the function to do all our OpenGL drawing. */
	glutDisplayFunc(&DrawGLScene);  

	/* Go fullscreen.  This is as soon as possible. */
	glutFullScreen();

	/* Even if there are no events, redraw our gl scene. */
	glutIdleFunc(&DrawGLScene);

	/* Register the function called when our window is resized. */
	glutReshapeFunc(&ReSizeGLScene);

	/* Register the function called when the keyboard is pressed. */
	glutKeyboardFunc(&keyPressed);

	/* Initialize our window. */
	InitGL(640, 480);

	/* Start Event Processing Engine */  
	glutMainLoop();  

	return 1;
}

